<!DOCTYPE html>
<html lang="zh_cn">
<head>
          <title>来玩魔王的咚</title>
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <meta charset="utf-8" />
        <!-- twitter card metadata -->
<meta name="twitter:card" content="summary">
<meta name="twitter:image" content="/images/mowang.png">
<meta name="twitter:site" content="">
<meta name="twitter:title" content="使用 cgo 调用 C 代码">
<meta name="twitter:description" content="<p>用cgo调用C代码：使用cgo的场景，C代码，cgo注释，自定义bzip包；总结；安装cgo环境：Windows平台安装gcc，错误信息，下载安装，设置环境变量，运行一个简单cgo程序</p>">
        <!-- OG Tags -->
<meta property="og:url" content="/gopl-calling-c-code-with-cgo.html"/>
<meta property="og:title" content="来玩魔王的咚 | 使用 cgo 调用 C 代码" />
<meta property="og:description" content="<p>用cgo调用C代码：使用cgo的场景，C代码，cgo注释，自定义bzip包；总结；安装cgo环境：Windows平台安装gcc，错误信息，下载安装，设置环境变量，运行一个简单cgo程序</p>" />
        <!-- favicon -->
        <link rel="icon" type="image/png" href="/images/mowang.png">
        <!-- moment.js for date formatting -->
        <script src="/theme/js/moment.js"></script>
        <!-- css -->
        <link rel="stylesheet" type="text/css" href="/theme/css/main.css" />
        <!-- 左边的menu，如果页面高度不够，就跟着滚动，否则文章分类显示不全 -->
        <link rel="stylesheet" type="text/css" href="/theme/css/mycss/menu.css" />
		<script>
			
                /*! grunt-grunticon Stylesheet Loader - v2.1.2 | https://github.com/filamentgroup/grunticon | (c) 2015 Scott Jehl, Filament Group, Inc. | MIT license. */
    
    (function(e){function t(t,n,r,o){"use strict";function a(){for(var e,n=0;u.length>n;n++)u[n].href&&u[n].href.indexOf(t)>-1&&(e=!0);e?i.media=r||"all":setTimeout(a)}var i=e.document.createElement("link"),l=n||e.document.getElementsByTagName("script")[0],u=e.document.styleSheets;return i.rel="stylesheet",i.href=t,i.media="only x",i.onload=o||null,l.parentNode.insertBefore(i,l),a(),i}var n=function(r,o){"use strict";if(r&&3===r.length){var a=e.navigator,i=e.Image,l=!(!document.createElementNS||!document.createElementNS("http://www.w3.org/2000/svg","svg").createSVGRect||!document.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#Image","1.1")||e.opera&&-1===a.userAgent.indexOf("Chrome")||-1!==a.userAgent.indexOf("Series40")),u=new i;u.onerror=function(){n.method="png",n.href=r[2],t(r[2])},u.onload=function(){var e=1===u.width&&1===u.height,a=r[e&&l?0:e?1:2];n.method=e&&l?"svg":e?"datapng":"png",n.href=a,t(a,null,null,o)},u.src="",document.documentElement.className+=" grunticon"}};n.loadCSS=t,e.grunticon=n})(this);(function(e,t){"use strict";var n=t.document,r="grunticon:",o=function(e){if(n.attachEvent?"complete"===n.readyState:"loading"!==n.readyState)e();else{var t=!1;n.addEventListener("readystatechange",function(){t||(t=!0,e())},!1)}},a=function(e){return t.document.querySelector('link[href$="'+e+'"]')},c=function(e){var t,n,o,a,c,i,u={};if(t=e.sheet,!t)return u;n=t.cssRules?t.cssRules:t.rules;for(var l=0;n.length>l;l++)o=n[l].cssText,a=r+n[l].selectorText,c=o.split(");")[0].match(/US\-ASCII\,([^"']+)/),c&&c[1]&&(i=decodeURIComponent(c[1]),u[a]=i);return u},i=function(e){var t,o,a;o="data-grunticon-embed";for(var c in e)if(a=c.slice(r.length),t=n.querySelectorAll(a+"["+o+"]"),t.length)for(var i=0;t.length>i;i++)t[i].innerHTML=e[c],t[i].style.backgroundImage="none",t[i].removeAttribute(o);return t},u=function(t){"svg"===e.method&&o(function(){i(c(a(e.href))),"function"==typeof t&&t()})};e.embedIcons=i,e.getCSS=a,e.getIcons=c,e.ready=o,e.svgLoadedCallback=u,e.embedSVG=u})(grunticon,this);
                
                grunticon(["/theme/css/icons.data.svg.css", "/theme/css/icons.data.png.css", "/theme/css/icons.fallback.css"]);
            </script>
        <noscript><link href="/theme/css/icons.fallback.css" rel="stylesheet"></noscript>
        <!-- menu toggle javascript -->
        <script type="text/javascript">
            document.addEventListener("DOMContentLoaded", initMenu);
            
            function initMenu(){
                var menu = document.getElementById("menu");
                var menulink = document.getElementById("menu-link");
                menulink.addEventListener("click", function toggleMenu(){
                        window.event.preventDefault();
                        menulink.classList.toggle('active');
                        menu.classList.toggle('active');              
                    });
            };
        </script>
        <!-- 不蒜子 -->
        <script async src="//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js"></script>

    <meta name="description" content="<p>用cgo调用C代码：使用cgo的场景，C代码，cgo注释，自定义bzip包；总结；安装cgo环境：Windows平台安装gcc，错误信息，下载安装，设置环境变量，运行一个简单cgo程序</p>" />

    <meta name="tags" content="gopl" />
    <meta name="tags" content="cgo" />
  <!-- 替换部分base的样式，看文章时，要再宽一点，右边有很多空间可以撑开 -->
  <link rel="stylesheet" type="text/css" href="/theme/css/mycss/article.css" />

</head>
<body>
    <div role="banner" id="masthead">
        <header>
            <a href="/"><img src="/images/mowang.png" alt="McManus Logo"></a>
                <h1>来玩魔王的咚@骑士救兵</h1>
            <a href="#menu" id="menu-link">more stuff</a>
            <nav id="menu">
                <ul>
                        <li><a href="/tags">tags</a></li>
                            <li><a href="/category/cloud.html">Cloud</a></li>
                            <li><a href="/category/docker.html">Docker</a></li>
                            <li class="active"><a href="/category/go.html">Go</a></li>
                            <li><a href="/category/linux.html">Linux</a></li>
                            <li><a href="/category/python.html">Python</a></li>
                            <li><a href="/category/xue-xi-bi-ji.html">学习笔记</a></li>
                            <li><a href="/category/yun-wei-zi-dong-hua.html">运维自动化</a></li>
                </ul>
            </nav>
        </header>
    </div>
        <div class="page" role="main">
  <div class="article" role="article">
    <article>
        <footer>
            <a name="top"></a>
            <p>
              <time datetime=" 2020-08-20 16:00:00+08:00">
                <script>document.write(moment('2020-08-20 16:00:00+08:00').format('LL'));</script>
              </time>
              ~
              <time datetime=" 2020-08-20 16:00:00+08:00">
                <script>document.write(moment('2020-08-20 16:00:00+08:00').format('LL'));</script>
              </time>
            </p>
        </footer>
        <header>
          <h2>
            使用 cgo 调用 C 代码
          </h2>
        </header>
      <div class="content">
         <div class="toc">
<ul>
<li><a href="#shi-yong-cgo-diao-yong-c-dai-ma">使用 cgo 调用 C 代码</a><ul>
<li><a href="#shi-yong-cgo-de-chang-jing">使用cgo的场景</a></li>
<li><a href="#c-dai-ma">C代码</a></li>
<li><a href="#cgo-zhu-shi">cgo注释</a></li>
<li><a href="#write-fang-fa">Write方法</a></li>
<li><a href="#close-fang-fa">Close方法</a></li>
<li><a href="#shi-yong-bzip-bao">使用bzip包</a></li>
<li><a href="#zong-jie">总结</a></li>
</ul>
</li>
<li><a href="#an-zhuang-cgo-huan-jing">安装cgo环境</a><ul>
<li><a href="#cuo-wu-xin-xi">错误信息</a></li>
<li><a href="#xia-zai-an-zhuang">下载安装</a></li>
<li><a href="#she-zhi-huan-jing-bian-liang">设置环境变量</a></li>
<li><a href="#yun-xing-yi-ge-jian-dan-cgo-cheng-xu">运行一个简单cgo程序</a></li>
</ul>
</li>
</ul>
</div>
<h3 id="shi-yong-cgo-diao-yong-c-dai-ma"><a class="toclink" href="#shi-yong-cgo-diao-yong-c-dai-ma">使用 cgo 调用 C 代码</a></h3>
<p>cgo 是用来为 C 函数创建 Go 绑定的工具。诸如此类的工具都叫作<strong>外部函数接口</strong>（FFI）。<br>
其他的工具还有，比如SWIG（sig.org）是另一个工具，它提供了更加复杂的特性用来集成C++的类，这个不讲。  </p>
<h4 id="shi-yong-cgo-de-chang-jing"><a class="toclink" href="#shi-yong-cgo-de-chang-jing">使用cgo的场景</a></h4>
<p>如果一个程序已经有现成的C语言的实现，但是还没有Go语言的实现的时候，那么有以下3种选择：</p>
<ol>
<li>如果是一个比较小的C语言库，可以使用纯 Go 语言来移植它（重新实现一遍）</li>
<li>如果性能不是很关键，可以用 os/exec 包以辅助子进程的方式来调用C程序</li>
<li>当需要使用复杂而且性能要求高的底层C接口时，就是使用cgo的场景了</li>
</ol>
<p>简单说就是，如果是简单的实现，那么就再造一个Go语言的轮子。如果性能要求不高，可以直接通过系统来调用这个程序。在不想重新造轮子又不想间接的通过系统来调用的时候，就需要用到 cgo 了。  </p>
<p>bzip2 压缩程序正是这样的一个情况。接下来就要使用 cgo 来构建一个简单的数据压缩程序。<br>
标准库的 compress/... 子包中提供了流行压缩算法的压缩器和解压缩器，包括流行的LZW压缩算法（Unix的compress命令用的算法）和DEFLATE压缩算法（GNU gzip命令用的算法）。这些包中的 API 有些许的不同，但都提供一个对 io.Writer 的封装用来对写入的数据进行压缩，并且还有一个对 io.Reader 的封装，在读取数据的同时进行压缩。<br>
例如：</p>
<div class="highlight"><pre><span></span><code><span class="kn">package</span><span class="w"> </span><span class="nx">gzip</span><span class="w"> </span><span class="c1">// compress/gzip</span><span class="w"></span>
<span class="kd">func</span><span class="w"> </span><span class="nx">NewWriter</span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">Writer</span><span class="p">)</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">WriteCloser</span><span class="w"></span>
<span class="kd">func</span><span class="w"> </span><span class="nx">NewReader</span><span class="p">(</span><span class="nx">r</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">Reader</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="nx">io</span><span class="p">.</span><span class="nx">ReadCloser</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"></span>
</code></pre></div>

<p>bzip2 算法基于优雅的 Burrows-Wheeler 变换，它和 gzip 相比速度要慢但是压缩比更高。标准库的 compress/bzip2 提供了 bzip2 的解压缩器，但是目前还没有提供压缩功能。从头开始实现这个压缩算法比较麻烦，而且 http://bzip.org 已经有现成的libbzip2的开源实现了，这是一个文档完善且高性能的开源 C 语言实现。  </p>
<p>要使用C语言的libbzip2包，需要先构建一个bz_stream结构体，这个结构体包含输入和输出缓冲区，以及三个C函数：</p>
<ul>
<li>BZ2_bzCompressInit: 初始化缓存，分配流的缓冲区</li>
<li>BZ2_bzCompress: 将输入缓存的数据压缩到输出缓存</li>
<li>BZ2_bzCompressEnd: 释放不需要的缓存</li>
</ul>
<h4 id="c-dai-ma"><a class="toclink" href="#c-dai-ma">C代码</a></h4>
<p>可以在Go代码中直接调用BZ2_bzCompressInit和BZ2_bzCompressEnd。<br>
但是对于BZ2_bzCompress，我们将定义一个C语言的包装函数，用它完成真正的工作。<br>
下面是C代码，和其他Go文件放在同一个包下：</p>
<div class="highlight"><pre><span></span><code><span class="c1">// bzip 包中的文件 bzip2.c</span>
<span class="c1">// 对 libbzip2 的简单包装，适合 cgo 使用</span>
<span class="cp">#include</span><span class="w"> </span><span class="cpf">&lt;bzlib.h&gt;</span><span class="cp"></span>

<span class="kt">int</span><span class="w"> </span><span class="nf">bz2compress</span><span class="p">(</span><span class="n">bz_stream</span><span class="w"> </span><span class="o">*</span><span class="n">s</span><span class="p">,</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="n">action</span><span class="p">,</span><span class="w"></span>
<span class="w">                </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">in</span><span class="p">,</span><span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="o">*</span><span class="n">inlen</span><span class="p">,</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="o">*</span><span class="n">out</span><span class="p">,</span><span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="o">*</span><span class="n">outlen</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">  </span><span class="n">s</span><span class="o">-&gt;</span><span class="n">next_in</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">in</span><span class="p">;</span><span class="w"></span>
<span class="w">  </span><span class="n">s</span><span class="o">-&gt;</span><span class="n">avail_in</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">*</span><span class="n">inlen</span><span class="p">;</span><span class="w"></span>
<span class="w">  </span><span class="n">s</span><span class="o">-&gt;</span><span class="n">next_out</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">out</span><span class="p">;</span><span class="w"></span>
<span class="w">  </span><span class="n">s</span><span class="o">-&gt;</span><span class="n">avail_out</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">*</span><span class="n">outlen</span><span class="p">;</span><span class="w"></span>
<span class="w">  </span><span class="kt">int</span><span class="w"> </span><span class="n">r</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">BZ2_bzCompress</span><span class="p">(</span><span class="n">s</span><span class="p">,</span><span class="w"> </span><span class="n">action</span><span class="p">);</span><span class="w"></span>
<span class="w">  </span><span class="o">*</span><span class="n">inlen</span><span class="w"> </span><span class="o">-=</span><span class="w"> </span><span class="n">s</span><span class="o">-&gt;</span><span class="n">avail_in</span><span class="p">;</span><span class="w"></span>
<span class="w">  </span><span class="o">*</span><span class="n">outlen</span><span class="w"> </span><span class="o">-=</span><span class="w"> </span><span class="n">s</span><span class="o">-&gt;</span><span class="n">avail_out</span><span class="p">;</span><span class="w"></span>
<span class="w">  </span><span class="n">s</span><span class="o">-&gt;</span><span class="n">next_in</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">s</span><span class="o">-&gt;</span><span class="n">next_out</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nb">NULL</span><span class="p">;</span><span class="w"></span>
<span class="w">  </span><span class="k">return</span><span class="w"> </span><span class="n">r</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p><strong>安装gcc</strong><br>
可能会出现如下的错误提示：</p>
<div class="highlight"><pre><span></span><code>exec: &quot;gcc&quot;: executable file not found in %PATH%
</code></pre></div>

<p>这个应该是缺少gcc编译器，所以需要安装配置好。在Windows系统上可能要麻烦一点，这个问题可以去看下一章节。不过即使Windows上解决了gcc的依赖，但还是没解决在Windows上安装bzip2。这个不折腾了，所以这个示例还是需要Linux系统。<br>
最后还会有一个简单的c程序，可以在Windows上验证cgo。  </p>
<h4 id="cgo-zhu-shi"><a class="toclink" href="#cgo-zhu-shi">cgo注释</a></h4>
<p>然后是Go代码，这里只是源码文件开头的部分，第一部分如下所示。<br>
声明 import "C" 很特别， 并没有这样的一个包，但是这会让编译程序在编译之前先运行cgo工具：</p>
<div class="highlight"><pre><span></span><code><span class="c1">// bzip 包中的文件 bzip2.go 的第一部分</span><span class="w"></span>

<span class="c1">// 包 bzip 封装了一个使用 bzip2 压缩算法的 writer (bzip.org).</span><span class="w"></span>
<span class="kn">package</span><span class="w"> </span><span class="nx">bzip</span><span class="w"></span>

<span class="cm">/*</span>
<span class="cm">#cgo CFLAGS: -I/usr/include</span>
<span class="cm">#cgo LDFLAGS: -L/usr/lib -lbz2</span>
<span class="cm">#include &lt;bzlib.h&gt;</span>
<span class="cm">#include &lt;stdlib.h&gt;</span>
<span class="cm">bz_stream* bz2alloc() { return calloc(1, sizeof(bz_stream)); }</span>
<span class="cm">int bz2compress(bz_stream *s, int action,</span>
<span class="cm">                char *in, unsigned *inlen, char *out, unsigned *outlen);</span>
<span class="cm">void bz2free(bz_stream* s) { free(s); }</span>
<span class="cm">*/</span><span class="w"></span>
<span class="kn">import</span><span class="w"> </span><span class="s">&quot;C&quot;</span><span class="w"></span>

<span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;io&quot;</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;unsafe&quot;</span><span class="w"></span>
<span class="p">)</span><span class="w"></span>

<span class="kd">type</span><span class="w"> </span><span class="nx">writer</span><span class="w"> </span><span class="kd">struct</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="nx">w</span><span class="w">      </span><span class="nx">io</span><span class="p">.</span><span class="nx">Writer</span><span class="w"> </span><span class="c1">// 基本输出流</span><span class="w"></span>
<span class="w">    </span><span class="nx">stream</span><span class="w"> </span><span class="o">*</span><span class="nx">C</span><span class="p">.</span><span class="nx">bz_stream</span><span class="w"></span>
<span class="w">    </span><span class="nx">outbuf</span><span class="w"> </span><span class="p">[</span><span class="mi">64</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">1024</span><span class="p">]</span><span class="kt">byte</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>

<span class="c1">// NewWriter 对于 bzip2 压缩的流返回一个 writer</span><span class="w"></span>
<span class="kd">func</span><span class="w"> </span><span class="nx">NewWriter</span><span class="p">(</span><span class="nx">out</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">Writer</span><span class="p">)</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">WriteCloser</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="kd">const</span><span class="w"> </span><span class="nx">blockSize</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">9</span><span class="w"></span>
<span class="w">    </span><span class="kd">const</span><span class="w"> </span><span class="nx">verbosity</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">0</span><span class="w"></span>
<span class="w">    </span><span class="kd">const</span><span class="w"> </span><span class="nx">workFactor</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="mi">30</span><span class="w"></span>
<span class="w">    </span><span class="nx">w</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">writer</span><span class="p">{</span><span class="nx">w</span><span class="p">:</span><span class="w"> </span><span class="nx">out</span><span class="p">,</span><span class="w"> </span><span class="nx">stream</span><span class="p">:</span><span class="w"> </span><span class="nx">C</span><span class="p">.</span><span class="nx">bz2alloc</span><span class="p">()}</span><span class="w"></span>
<span class="w">    </span><span class="nx">C</span><span class="p">.</span><span class="nx">BZ2_bzCompressInit</span><span class="p">(</span><span class="nx">w</span><span class="p">.</span><span class="nx">stream</span><span class="p">,</span><span class="w"> </span><span class="nx">blockSize</span><span class="p">,</span><span class="w"> </span><span class="nx">verbosity</span><span class="p">,</span><span class="w"> </span><span class="nx">workFactor</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">w</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p>在预处理过程中，cgo 产生一个临时包，这个包里包含了所有C语言的函数和类型对应的Go语言声明。例如 C.bz_stream 和 C.BZ2_bzCompressInit。cgo 工具通过以一种特殊的方式调用C编译器来发现在Go源文件中 <code>import "C"</code> 声明之前的注释中包含的C头文件中的内容。  </p>
<p>在cgo注释中还可以包含 #cgo 指令，用来指定C工具链中其他的选项。CFLAGS 和 LDFLAGS 分别对应传给C语言编译器的编译参数和链接器参数，使它们可以从特定目录找到bzlib.h头文件和libbz2.a库文件。这个例子假定已经在 <code>/usr</code> 目录成功安装了bzip2库。根据个人的安装情况，可以修改或者删除这些标记。（这里还有一个纯C生成的cgo绑定，不依赖bzip2静态库和操作系统的具体环境，具体访问github： https://github.com/chai2010/bzip2 ，这里就顺带提一下现在有更方便的实现方式了）  </p>
<p>NewWriter 调用C函数 BZ2_bzCompressInit 来初始化流的缓冲区。在 writer 结构体中还包含一个额外的缓冲区用来耗尽解压缩器的输出缓冲区。  </p>
<h4 id="write-fang-fa"><a class="toclink" href="#write-fang-fa">Write方法</a></h4>
<p>下面所示的 Write 方法将未解压的数据写入压缩器中，然后在一个循环中调用 bz2compress 函数，直到所有的数据压缩完毕。Go程序可以访问C的类型（比如 bz_stream、char 和 uint），C的函数（比如 bz2compress），甚至是类似C的预处理宏的对象（比如 BZ_RUN），这些都通过 C.x 的方式来访问。即使类型 C.unit 和 Go 的 uint 长度相同，它们的类型也是不同的：</p>
<div class="highlight"><pre><span></span><code><span class="c1">// bzip 包中的文件 bzip2.go 的第二部分</span><span class="w"></span>

<span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="o">*</span><span class="nx">writer</span><span class="p">)</span><span class="w"> </span><span class="nx">Write</span><span class="p">(</span><span class="nx">data</span><span class="w"> </span><span class="p">[]</span><span class="kt">byte</span><span class="p">)</span><span class="w"> </span><span class="p">(</span><span class="kt">int</span><span class="p">,</span><span class="w"> </span><span class="kt">error</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">w</span><span class="p">.</span><span class="nx">stream</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nb">panic</span><span class="p">(</span><span class="s">&quot;closed&quot;</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="kd">var</span><span class="w"> </span><span class="nx">total</span><span class="w"> </span><span class="kt">int</span><span class="w"> </span><span class="c1">// 写入的未压缩字节数</span><span class="w"></span>

<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="nb">len</span><span class="p">(</span><span class="nx">data</span><span class="p">)</span><span class="w"> </span><span class="p">&gt;</span><span class="w"> </span><span class="mi">0</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">inlen</span><span class="p">,</span><span class="w"> </span><span class="nx">outlen</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">C</span><span class="p">.</span><span class="nb">uint</span><span class="p">(</span><span class="nb">len</span><span class="p">(</span><span class="nx">data</span><span class="p">)),</span><span class="w"> </span><span class="nx">C</span><span class="p">.</span><span class="nb">uint</span><span class="p">(</span><span class="nb">cap</span><span class="p">(</span><span class="nx">w</span><span class="p">.</span><span class="nx">outbuf</span><span class="p">))</span><span class="w"></span>
<span class="w">        </span><span class="nx">C</span><span class="p">.</span><span class="nx">bz2compress</span><span class="p">(</span><span class="nx">w</span><span class="p">.</span><span class="nx">stream</span><span class="p">,</span><span class="w"> </span><span class="nx">C</span><span class="p">.</span><span class="nx">BZ_RUN</span><span class="p">,</span><span class="w"></span>
<span class="w">            </span><span class="p">(</span><span class="o">*</span><span class="nx">C</span><span class="p">.</span><span class="nx">char</span><span class="p">)(</span><span class="nx">unsafe</span><span class="p">.</span><span class="nx">Pointer</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">data</span><span class="p">[</span><span class="mi">0</span><span class="p">])),</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">inlen</span><span class="p">,</span><span class="w"></span>
<span class="w">            </span><span class="p">(</span><span class="o">*</span><span class="nx">C</span><span class="p">.</span><span class="nx">char</span><span class="p">)(</span><span class="nx">unsafe</span><span class="p">.</span><span class="nx">Pointer</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">w</span><span class="p">.</span><span class="nx">outbuf</span><span class="p">)),</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">outlen</span><span class="p">)</span><span class="w"></span>
<span class="w">        </span><span class="nx">total</span><span class="w"> </span><span class="o">+=</span><span class="w"> </span><span class="nb">int</span><span class="p">(</span><span class="nx">inlen</span><span class="p">)</span><span class="w"></span>
<span class="w">        </span><span class="nx">data</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="nx">data</span><span class="p">[</span><span class="nx">inlen</span><span class="p">:]</span><span class="w"></span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">w</span><span class="p">.</span><span class="nx">w</span><span class="p">.</span><span class="nx">Write</span><span class="p">(</span><span class="nx">w</span><span class="p">.</span><span class="nx">outbuf</span><span class="p">[:</span><span class="nx">outlen</span><span class="p">]);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="nx">total</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="k">return</span><span class="w"> </span><span class="nx">total</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p>每一次的迭代首先计算传说数据 data 剩余的长度以及输出缓冲 w.outbuf 的容量。然后把这两个值的地址以及 data 和 w.outbuf 的地址都传递给 bz2compress 函数。两个长度信息传地址而不传值，这样C函数就可以更新这两个值。这两个值记录的分别是已压缩的数据和压缩后数据的大小。然后把每块压缩后的数据写入底层的 io.Writer（w.w.Write方法）。  </p>
<h4 id="close-fang-fa"><a class="toclink" href="#close-fang-fa">Close方法</a></h4>
<p>Close方法和Write方法结构类似，通过一个循环将剩余的压缩后的数据从输出缓冲区写入底层：</p>
<div class="highlight"><pre><span></span><code><span class="c1">// bzip 包中的文件 bzip2.go 的第三部分</span><span class="w"></span>

<span class="c1">// Close 方法清空压缩的数据并关闭流</span><span class="w"></span>
<span class="c1">// 它不会关闭底层的 io.Writer</span><span class="w"></span>
<span class="kd">func</span><span class="w"> </span><span class="p">(</span><span class="nx">w</span><span class="w"> </span><span class="o">*</span><span class="nx">writer</span><span class="p">)</span><span class="w"> </span><span class="nx">Close</span><span class="p">()</span><span class="w"> </span><span class="kt">error</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">w</span><span class="p">.</span><span class="nx">stream</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nb">panic</span><span class="p">(</span><span class="s">&quot;closed&quot;</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="k">defer</span><span class="w"> </span><span class="kd">func</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">C</span><span class="p">.</span><span class="nx">BZ2_bzCompressEnd</span><span class="p">(</span><span class="nx">w</span><span class="p">.</span><span class="nx">stream</span><span class="p">)</span><span class="w"></span>
<span class="w">        </span><span class="nx">C</span><span class="p">.</span><span class="nx">bz2free</span><span class="p">(</span><span class="nx">w</span><span class="p">.</span><span class="nx">stream</span><span class="p">)</span><span class="w"></span>
<span class="w">        </span><span class="nx">w</span><span class="p">.</span><span class="nx">stream</span><span class="w"> </span><span class="p">=</span><span class="w"> </span><span class="kc">nil</span><span class="w"></span>
<span class="w">    </span><span class="p">}()</span><span class="w"></span>
<span class="w">    </span><span class="k">for</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">inlen</span><span class="p">,</span><span class="w"> </span><span class="nx">outlen</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">C</span><span class="p">.</span><span class="nb">uint</span><span class="p">(</span><span class="mi">0</span><span class="p">),</span><span class="w"> </span><span class="nx">C</span><span class="p">.</span><span class="nb">uint</span><span class="p">(</span><span class="nb">cap</span><span class="p">(</span><span class="nx">w</span><span class="p">.</span><span class="nx">outbuf</span><span class="p">))</span><span class="w"></span>
<span class="w">        </span><span class="nx">r</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">C</span><span class="p">.</span><span class="nx">bz2compress</span><span class="p">(</span><span class="nx">w</span><span class="p">.</span><span class="nx">stream</span><span class="p">,</span><span class="w"> </span><span class="nx">C</span><span class="p">.</span><span class="nx">BZ_FINISH</span><span class="p">,</span><span class="w"> </span><span class="kc">nil</span><span class="p">,</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">inlen</span><span class="p">,</span><span class="w"></span>
<span class="w">            </span><span class="p">(</span><span class="o">*</span><span class="nx">C</span><span class="p">.</span><span class="nx">char</span><span class="p">)(</span><span class="nx">unsafe</span><span class="p">.</span><span class="nx">Pointer</span><span class="p">(</span><span class="o">&amp;</span><span class="nx">w</span><span class="p">.</span><span class="nx">outbuf</span><span class="p">)),</span><span class="w"> </span><span class="o">&amp;</span><span class="nx">outlen</span><span class="p">)</span><span class="w"></span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">w</span><span class="p">.</span><span class="nx">w</span><span class="p">.</span><span class="nx">Write</span><span class="p">(</span><span class="nx">w</span><span class="p">.</span><span class="nx">outbuf</span><span class="p">[:</span><span class="nx">outlen</span><span class="p">]);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="nx">err</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>
<span class="w">        </span><span class="k">if</span><span class="w"> </span><span class="nx">r</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="nx">C</span><span class="p">.</span><span class="nx">BZ_STREAM_END</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">            </span><span class="k">return</span><span class="w"> </span><span class="kc">nil</span><span class="w"></span>
<span class="w">        </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p>压缩完成后，Close 方法最后会调用 C.BZ2_bzCompressEnd 来释放流缓冲区，这写语句写在 defer 中来确保所有路径返回后都会释放资源。这个时候，w.stream 指针就不能安全地解引用了，要把它设置为 nil，并且在方法调用的开头添加显式的 nil 检查。这样如果用户在 Close 之后错误地调用方法，程序就会panic。  </p>
<h4 id="shi-yong-bzip-bao"><a class="toclink" href="#shi-yong-bzip-bao">使用bzip包</a></h4>
<p>下面的程序，使用上面的程序包实现bzip2压缩命令。用起来和很多UNIX系统上面的 bzip2 命令相似：</p>
<div class="highlight"><pre><span></span><code><span class="c1">// bzipper 读取输入、使用 bzip2 压缩然后输出数据</span><span class="w"></span>
<span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w"></span>

<span class="kn">import</span><span class="w"> </span><span class="p">(</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;io&quot;</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;log&quot;</span><span class="w"></span>
<span class="w">    </span><span class="s">&quot;os&quot;</span><span class="w"></span>

<span class="w">    </span><span class="s">&quot;gopl/bzip&quot;</span><span class="w"></span>
<span class="p">)</span><span class="w"></span>

<span class="kd">func</span><span class="w"> </span><span class="nx">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="nx">w</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">bzip</span><span class="p">.</span><span class="nx">NewWriter</span><span class="p">(</span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdout</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">_</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">io</span><span class="p">.</span><span class="nx">Copy</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span><span class="w"> </span><span class="nx">os</span><span class="p">.</span><span class="nx">Stdin</span><span class="p">);</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">log</span><span class="p">.</span><span class="nx">Fatalf</span><span class="p">(</span><span class="s">&quot;bzipper: %v\n&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="w">    </span><span class="k">if</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">:=</span><span class="w"> </span><span class="nx">w</span><span class="p">.</span><span class="nx">Close</span><span class="p">();</span><span class="w"> </span><span class="nx">err</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="kc">nil</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">        </span><span class="nx">log</span><span class="p">.</span><span class="nx">Fatalf</span><span class="p">(</span><span class="s">&quot;bzipper: close: %v\n&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">err</span><span class="p">)</span><span class="w"></span>
<span class="w">    </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<h4 id="zong-jie"><a class="toclink" href="#zong-jie">总结</a></h4>
<p>这里演示了如何将C库链接进Go程序中。（<em>反过来，可以将Go程序编译为静态库然后链接进C程序中，也可以编译为动态库通过C程序来加载和共享</em>）<br>
另外还有一些别的问题。  </p>
<h5 id="mei-you-bzip2-ku"><a class="toclink" href="#mei-you-bzip2-ku">没有bzip2库</a></h5>
<p>这里的例子是假设已经安装了 bzip2 库。如果是安装位置不对，可以修改 #cgo 来解决。另外，也有人提供了不用依赖本机上的 bzip2 库的实现。<br>
这里有一个从纯C代码生成的cgo绑定，不依赖bzip2静态库和操作系统的具体环境，具体请访问 <a href="https://github.com/chai2010/bzip2">https://github.com/chai2010/bzip2</a>  </p>
<h5 id="bing-fa-an-quan-wen-ti"><a class="toclink" href="#bing-fa-an-quan-wen-ti">并发安全问题</a></h5>
<p>上面的实现中，结构体 writer 不是并发安全的。并且并发调用 Close 和 Write 也会导致C代码崩溃。这个问题可以用加锁的方式来避免，使用 sync.Mutex 可以保证 bzip2.writer 在多个goroutines中被并发调用是安全的。  </p>
<h5 id="osexec-bao-de-shi-xian"><a class="toclink" href="#osexec-bao-de-shi-xian">os/exec 包的实现</a></h5>
<p>开篇提到了还有一种实现方式：用 os/exec 包以辅助子进程的方式来调用C程序。<br>
可以使用 os/exec 包将 /bin/bzip2 命令作为一个子进程执行。实现一个纯Go的 bzip.NewWriter 来替代原来的实现。这样就是一个纯Go的实现，不需要C言语的基础。不过虽然是纯Go的实现，但还是要依赖本机能够运行 /bin/bzaip2 命令的。  </p>
<h3 id="an-zhuang-cgo-huan-jing"><a class="toclink" href="#an-zhuang-cgo-huan-jing">安装cgo环境</a></h3>
<p>MinGW（Minimalist GNU For Windows），是个精简的Windows平台C/C++、ADA及Fortran编译器，相比Cygwin而言，体积要小很多，使用较为方便。<br>
实际上也没那么方便...  </p>
<h4 id="cuo-wu-xin-xi"><a class="toclink" href="#cuo-wu-xin-xi">错误信息</a></h4>
<p>首先，会遇到下面的报错：</p>
<div class="highlight"><pre><span></span><code>exec: &quot;gcc&quot;: executable file not found in %PATH%
</code></pre></div>

<p>这是因为缺少gcc编译器。  </p>
<p>另外如果装好了，可能还会遇到这个问题：</p>
<div class="highlight"><pre><span></span><code>cc1.exe: sorry, unimplemented: 64-bit mode not compiled in
</code></pre></div>

<p>这是因为需要安装一个64位的版本。  </p>
<p>最后还会有一个小问题的报错，类似下面这样：</p>
<div class="highlight"><pre><span></span><code>.\main.go:9:45: could not determine kind of name for C.FLT_MAX
</code></pre></div>

<p>看这个报错的内容是我们的代码的问题。其实就是 cgo 注释必须紧挨着 <code>import "C"</code>，中间不能有空行。  </p>
<h4 id="xia-zai-an-zhuang"><a class="toclink" href="#xia-zai-an-zhuang">下载安装</a></h4>
<p>可以去这里下载：<br>
<a href="https://sourceforge.net/projects/mingw-w64/">https://sourceforge.net/projects/mingw-w64/</a><br>
不过这只是一个在线安装程序。应该是国外的资源，在线安装无法成功。<br>
还可以直接去下载编译好的版本，资源都在页面的Files分页里查找。因为有一些编译的选项来适配各种系统和版本，下载了下面路径下的文件：</p>
<div class="highlight"><pre><span></span><code>Home/Toolchains targetting Win64/Personal Builds/mingw-builds/8.1.0/threads-win32/seh/
</code></pre></div>

<p>文件名是这个：<code>x86_64-8.1.0-release-win32-seh-rt_v6-rev0.7z</code><br>
具体的下载地址是：<br>
<a href="https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/7.3.0/threads-win32/seh/x86_64-7.3.0-release-win32-seh-rt_v5-rev0.7z">https://sourceforge.net/projects/mingw-w64/files/Toolchains%20targetting%20Win64/Personal%20Builds/mingw-builds/7.3.0/threads-win32/seh/x86_64-7.3.0-release-win32-seh-rt_v5-rev0.7z</a><br>
因为是编译好的版本，无需安装直接解压就可以了。  </p>
<h4 id="she-zhi-huan-jing-bian-liang"><a class="toclink" href="#she-zhi-huan-jing-bian-liang">设置环境变量</a></h4>
<p>解压后放在系统的某个目录下，比如 gcc.exe 这个文件的路径是这个：<code>D:\MinGW\bin\gcc.exe</code>。下面就按这个路径来设置环境变量。<br>
我的电脑-&gt;属性-&gt;高级系统设置，在“高级”分页下的“环境变量...”里就可以设置环境变量。<br>
添加一条环境变量：</p>
<table>
<thead>
<tr>
<th>变量（KET）</th>
<th>值（VALUE）</th>
</tr>
</thead>
<tbody>
<tr>
<td>MinGW</td>
<td>D:\MinGW</td>
</tr>
</tbody>
</table>
<p>然后在已有的环境变量 <code>Path</code> 里添加一项 <code>%MinGW%\bin</code>，到此设置完成。  </p>
<table>
<thead>
<tr>
<th>变量（KET）</th>
<th>值（VALUE）</th>
</tr>
</thead>
<tbody>
<tr>
<td>Path</td>
<td>省略其他已有的值...;%MinGW%\bin;</td>
</tr>
</tbody>
</table>
<h4 id="yun-xing-yi-ge-jian-dan-cgo-cheng-xu"><a class="toclink" href="#yun-xing-yi-ge-jian-dan-cgo-cheng-xu">运行一个简单cgo程序</a></h4>
<p>下面是一段简单的cgo代码：</p>
<div class="highlight"><pre><span></span><code><span class="kn">package</span><span class="w"> </span><span class="nx">main</span><span class="w"></span>

<span class="c1">// #include &lt;float.h&gt;</span><span class="w"></span>
<span class="kn">import</span><span class="w"> </span><span class="s">&quot;C&quot;</span><span class="w"></span>
<span class="kn">import</span><span class="w"> </span><span class="s">&quot;fmt&quot;</span><span class="w"></span>

<span class="kd">func</span><span class="w"> </span><span class="nx">main</span><span class="p">()</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w">    </span><span class="nx">fmt</span><span class="p">.</span><span class="nx">Println</span><span class="p">(</span><span class="s">&quot;Max float value of float is&quot;</span><span class="p">,</span><span class="w"> </span><span class="nx">C</span><span class="p">.</span><span class="nx">FLT_MAX</span><span class="p">)</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>

<p>就像普通的Go程序那样编译运行就好了。  </p>
      </div>
      <div class="back-to-top">
        <a href="/">HOME</a>
        <a href="#top">TOP</a>
      </div>
    </article>
  </div>
<!-- end article -->
<!-- 页面往下滚动一段之后才会显示TOC -->
<script>
  window.onscroll = function() {
    var tocbox = document.getElementsByClassName('toc')[0];
    var osTop = document.documentElement.scrollTop || document.body.scrollTop;
    var osWidth = document.documentElement.scrollWidth || document.body.scrollWidth;
    // console.log(osTop)
    if (osTop>300 && osWidth>865) {
      tocbox.style.display = "block"
    }
    if (osTop<300 || osWidth<865) {
      tocbox.style.display = "none"
    }
  }
</script>
                <footer>
                    <div class="icons">
                    </div>
                    <span id="busuanzi_container_page_pv" style="padding: 10px">本文阅读量<span id="busuanzi_value_page_pv"></span>次</span>
                    <span id="busuanzi_container_site_pv" style="padding: 10px">本站总访问量<span id="busuanzi_value_site_pv"></span>次</span>
                    <span id="busuanzi_container_site_uv" style="padding: 10px">本站总访客数<span id="busuanzi_value_site_uv"></span>人</span>
                    <p>© <script>document.write(moment().format('YYYY'));</script> 749B</p>
                </footer>
        </div>
</body>
</html>